﻿using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Pipelines;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace JTActiveSafety.Gateway.Internal
{
    internal abstract partial class TerminalProtocol : IHttpResponseControl
    {
        private Stack<KeyValuePair<Func<object, Task>, object>> _onStarting;
        private Stack<KeyValuePair<Func<object, Task>, object>> _onCompleted;

        private readonly object _abortLock = new object();
        protected volatile bool _connectionAborted;
        private bool _preventRequestAbortedCancellation;
        private CancellationTokenSource _abortedCts;
        private CancellationToken? _manuallySetRequestAbortToken;

        protected RequestProcessingStatus _requestProcessingStatus;

        // Keep-alive is default for HTTP/1.1 and HTTP/2; parsing and errors will change its value
        // volatile, see: https://msdn.microsoft.com/en-us/library/x13ttww7.aspx
        protected volatile bool _keepAlive = true;
        // _canWriteResponseBody is set in CreateResponseHeaders.
        // If we are writing with GetMemory/Advance before calling StartAsync, assume we can write and throw away contents if we can't.
        private bool _canWriteResponseBody = true;
        private bool _hasAdvanced;
        private bool _isLeasedMemoryInvalid = true;
        private bool _autoChunk;
        protected Exception _applicationException;
        private BadTerminalRequestException _requestRejectedException;

 
        private string _httpProtocol;

        private string _requestId;
        private int _requestHeadersParsed;

        private long _responseBytesWritten;

        private HttpConnectionContext _context;
        //private RouteValueDictionary _routeValues;
        //private Endpoint _endpoint;

        protected string _methodText = null;
        private string _scheme = null;
        private Stream _requestStreamInternal;
        private Stream _responseStreamInternal;

        public void Initialize(HttpConnectionContext context)
        {
            _context = context;

            ServerOptions = ServiceContext.ServerOptions;

            Reset();

            HttpResponseControl = this;
        }

        public IHttpResponseControl HttpResponseControl { get; set; }

        public ServiceContext ServiceContext => _context.ServiceContext;
        private IPEndPoint LocalEndPoint => _context.LocalEndPoint;
        private IPEndPoint RemoteEndPoint => _context.RemoteEndPoint;
        public ITimeoutControl TimeoutControl => _context.TimeoutControl;

        public IFeatureCollection ConnectionFeatures => _context.ConnectionFeatures;
        public IHttpOutputProducer Output { get; protected set; }

        protected IKestrelTrace Log => ServiceContext.Log;
        //private DateHeaderValueManager DateHeaderValueManager => ServiceContext.DateHeaderValueManager;
        // Hold direct reference to ServerOptions since this is used very often in the request processing path
        protected KestrelServerOptions ServerOptions { get; set; }
        protected string ConnectionId => _context.ConnectionId;

        public string ConnectionIdFeature { get; set; }
        public bool HasStartedConsumingRequestBody { get; set; }
        public long? MaxRequestBodySize { get; set; }
        public MinDataRate MinRequestBodyDataRate { get; set; }
        public bool AllowSynchronousIO { get; set; }

        /// <summary>
        /// The request id. <seealso cref="HttpContext.TraceIdentifier"/>
        /// </summary>
        public string TraceIdentifier
        {
            set => _requestId = value;
            get
            {
                // don't generate an ID until it is requested
                if (_requestId == null)
                {
                    _requestId = CreateRequestId();
                }
                return _requestId;
            }
        }

        public bool IsUpgradableRequest { get; private set; }
        public bool IsUpgraded { get; set; }
        public IPAddress RemoteIpAddress { get; set; }
        public int RemotePort { get; set; }
        public IPAddress LocalIpAddress { get; set; }
        public int LocalPort { get; set; }
  
 
        public bool RequestTrailersAvailable { get; set; }
        public Stream RequestBody { get; set; }
        public PipeReader RequestBodyPipeReader { get; set; }
 
        private int _statusCode;
        public int StatusCode
        {
            get => _statusCode;
            set
            {
                //if (HasResponseStarted)
                //{
                //    ThrowResponseAlreadyStartedException(nameof(StatusCode));
                //}

                _statusCode = value;
            }
        }

        private string _reasonPhrase;

        public string ReasonPhrase
        {
            get => _reasonPhrase;

            set
            {
                //if (HasResponseStarted)
                //{
                //    ThrowResponseAlreadyStartedException(nameof(ReasonPhrase));
                //}

                _reasonPhrase = value;
            }
        }
 
        public Stream ResponseBody { get; set; }
        public PipeWriter ResponseBodyPipeWriter { get; set; }

        public CancellationToken RequestAborted
        {
            get
            {
                // If a request abort token was previously explicitly set, return it.
                if (_manuallySetRequestAbortToken.HasValue)
                {
                    return _manuallySetRequestAbortToken.Value;
                }

                lock (_abortLock)
                {
                    if (_preventRequestAbortedCancellation)
                    {
                        return new CancellationToken(false);
                    }

                    if (_connectionAborted)
                    {
                        return new CancellationToken(true);
                    }

                    if (_abortedCts == null)
                    {
                        _abortedCts = new CancellationTokenSource();
                    }

                    return _abortedCts.Token;
                }
            }
            set
            {
                // Set an abort token, overriding one we create internally.  This setter and associated
                // field exist purely to support IHttpRequestLifetimeFeature.set_RequestAborted.
                _manuallySetRequestAbortToken = value;
            }
        }
 
        // For testing
        internal void ResetState()
        {
            _requestProcessingStatus = RequestProcessingStatus.RequestPending;
        }

        public void Reset()
        {
            _onStarting?.Clear();
            _onCompleted?.Clear();
 
            _requestProcessingStatus = RequestProcessingStatus.RequestPending;
            _autoChunk = false;
            _applicationException = null;
            _requestRejectedException = null;

 
            HasStartedConsumingRequestBody = false;
            MaxRequestBodySize = ServerOptions.Limits.MaxRequestBodySize;
            MinRequestBodyDataRate = ServerOptions.Limits.MinRequestBodyDataRate;
            AllowSynchronousIO = ServerOptions.AllowSynchronousIO;
            TraceIdentifier = null;
 
            _methodText = null;
            //_endpoint = null;
 
            _httpProtocol = null;
            _statusCode = 200;
            _reasonPhrase = null;

            var remoteEndPoint = RemoteEndPoint;
            RemoteIpAddress = remoteEndPoint?.Address;
            RemotePort = remoteEndPoint?.Port ?? 0;
            var localEndPoint = LocalEndPoint;
            LocalIpAddress = localEndPoint?.Address;
            LocalPort = localEndPoint?.Port ?? 0;

            ConnectionIdFeature = ConnectionId;
 
            RequestTrailersAvailable = false;

            _isLeasedMemoryInvalid = true;
            _hasAdvanced = false;
            _canWriteResponseBody = true;

 
            _manuallySetRequestAbortToken = null;

            // Lock to prevent CancelRequestAbortedToken from attempting to cancel a disposed CTS.
            CancellationTokenSource localAbortCts = null;

            lock (_abortLock)
            {
                _preventRequestAbortedCancellation = false;
                localAbortCts = _abortedCts;
                _abortedCts = null;
            }

            localAbortCts?.Dispose();

            Output?.Reset();

            _requestHeadersParsed = 0;

            _responseBytesWritten = 0;

            OnReset();
        }

        protected abstract void OnReset();

        protected abstract void ApplicationAbort();

        protected virtual void OnRequestProcessingEnding()
        {
        }

        protected virtual void OnRequestProcessingEnded()
        {
        }

        protected virtual void BeginRequestProcessing()
        {
        }

        protected virtual void OnErrorAfterResponseStarted()
        {
        }

        protected virtual bool BeginRead(out ValueTask<ReadResult> awaitable)
        {
            awaitable = default;
            return false;
        }

        protected abstract string CreateRequestId();

 
        protected abstract bool TryParseRequest(ReadResult result, out bool endConnection);

        private void CancelRequestAbortedTokenCallback()
        {
            try
            {
                CancellationTokenSource localAbortCts = null;

                lock (_abortLock)
                {
                    if (_abortedCts != null && !_preventRequestAbortedCancellation)
                    {
                        localAbortCts = _abortedCts;
                        _abortedCts = null;
                    }
                }

                // If we cancel the cts, we don't dispose as people may still be using
                // the cts. It also isn't necessary to dispose a canceled cts.
                localAbortCts?.Cancel();
            }
            catch (Exception ex)
            {
                Log.ApplicationError(ConnectionId, TraceIdentifier, ex);
            }
        }

        protected void CancelRequestAbortedToken()
        {
            var shouldScheduleCancellation = false;

            lock (_abortLock)
            {
                if (_connectionAborted)
                {
                    return;
                }

                shouldScheduleCancellation = _abortedCts != null && !_preventRequestAbortedCancellation;
                _connectionAborted = true;
            }

            if (shouldScheduleCancellation)
            {
                // Potentially calling user code. CancelRequestAbortedToken logs any exceptions.
                ServiceContext.Scheduler.Schedule(state => ((TerminalProtocol)state).CancelRequestAbortedTokenCallback(), this);
            }
        }

 
        // Prevents the RequestAborted token from firing for the duration of the request.
        private void PreventRequestAbortedCancellation()
        {
            lock (_abortLock)
            {
                if (_connectionAborted)
                {
                    return;
                }

                _preventRequestAbortedCancellation = true;
            }
        }
 

 

 
 
        public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> application)
        {
            try
            {
                // We run the request processing loop in a seperate async method so per connection
                // exception handling doesn't complicate the generated asm for the loop.
                await ProcessRequests(application);
            }
            //catch (BadHttpRequestException ex)
            //{
            //    // Handle BadHttpRequestException thrown during request line or header parsing.
            //    // SetBadRequestState logs the error.
            //    SetBadRequestState(ex);
            //}
            catch (ConnectionResetException ex)
            {
                // Don't log ECONNRESET errors made between requests. Browsers like IE will reset connections regularly.
                //if (_requestProcessingStatus != RequestProcessingStatus.RequestPending)
                //{
                //    Log.RequestProcessingError(ConnectionId, ex);
                //}
            }
            catch (IOException ex)
            {
                Log.RequestProcessingError(ConnectionId, ex);
            }
            catch (ConnectionAbortedException ex)
            {
                Log.RequestProcessingError(ConnectionId, ex);
            }
            catch (Exception ex)
            {
                Log.LogWarning(0, ex, "Request Processing End Error");
            }
            finally
            {
                try
                {
                    await TryProduceInvalidRequestResponse();
                }
                catch (Exception ex)
                {
                    Log.LogWarning(0, ex, "ConnectionShutdownError");
                }
                finally
                {
                    OnRequestProcessingEnded();
                }
            }
        }

        private Task TryProduceInvalidRequestResponse()
        {
            throw new NotImplementedException();
        }

        private async Task ProcessRequests<TContext>(IHttpApplication<TContext> application)
        {
            while (_keepAlive)
            {
                if (_context.InitialExecutionContext is null)
                {
                    // If this is a first request on a non-Http2Connection, capture a clean ExecutionContext.
                    _context.InitialExecutionContext = ExecutionContext.Capture();
                }
                else
                {
                    // Clear any AsyncLocals set during the request; back to a clean state ready for next request
                    // And/or reset to Http2Connection's ExecutionContext giving access to the connection logging scope
                    // and any other AsyncLocals set by connection middleware.
                    ExecutionContext.Restore(_context.InitialExecutionContext);
                }

                BeginRequestProcessing();

                var result = default(ReadResult);
                bool endConnection;
                do
                {
                    if (BeginRead(out var awaitable))
                    {
                        result = await awaitable;
                    }
                } while (!TryParseRequest(result, out endConnection));

                if (endConnection)
                {
                    // Connection finished, stop processing requests
                    return;
                }
 
                //IsUpgradableRequest = messageBody.RequestUpgrade;

                //InitializeBodyControl(messageBody);


                //var context = application.CreateContext(this);

                //try
                //{
                //    KestrelEventSource.Log.RequestStart(this);

                //    // Run the application code for this request
                //    await application.ProcessRequestAsync(context);

                //    // Trigger OnStarting if it hasn't been called yet and the app hasn't
                //    // already failed. If an OnStarting callback throws we can go through
                //    // our normal error handling in ProduceEnd.
                //    // https://github.com/aspnet/KestrelHttpServer/issues/43
                //    if (!HasResponseStarted && _applicationException == null && _onStarting?.Count > 0)
                //    {
                //        await FireOnStarting();
                //    }

                //    if (!_connectionAborted && !VerifyResponseContentLength(out var lengthException))
                //    {
                //        ReportApplicationError(lengthException);
                //    }
                //}
                //catch (BadTerminalRequestException ex)
                //{
                //    // Capture BadHttpRequestException for further processing
                //    // This has to be caught here so StatusCode is set properly before disposing the HttpContext
                //    // (DisposeContext logs StatusCode).
                //    SetBadRequestState(ex);
                //    ReportApplicationError(ex);
                //}
                //catch (Exception ex)
                //{
                //    ReportApplicationError(ex);
                //}

                //KestrelEventSource.Log.RequestStop(this);

                //// At this point all user code that needs use to the request or response streams has completed.
                //// Using these streams in the OnCompleted callback is not allowed.
                //try
                //{
                //    await _bodyControl.StopAsync();
                //}
                //catch (Exception ex)
                //{
                //    // BodyControl.StopAsync() can throw if the PipeWriter was completed prior to the application writing
                //    // enough bytes to satisfy the specified Content-Length. This risks double-logging the exception,
                //    // but this scenario generally indicates an app bug, so I don't want to risk not logging it.
                //    ReportApplicationError(ex);
                //}

                //// 4XX responses are written by TryProduceInvalidRequestResponse during connection tear down.
                //if (_requestRejectedException == null)
                //{
                //    if (!_connectionAborted)
                //    {
                //        // Call ProduceEnd() before consuming the rest of the request body to prevent
                //        // delaying clients waiting for the chunk terminator:
                //        //
                //        // https://github.com/dotnet/corefx/issues/17330#issuecomment-288248663
                //        //
                //        // This also prevents the 100 Continue response from being sent if the app
                //        // never tried to read the body.
                //        // https://github.com/aspnet/KestrelHttpServer/issues/2102
                //        //
                //        // ProduceEnd() must be called before _application.DisposeContext(), to ensure
                //        // HttpContext.Response.StatusCode is correctly set when
                //        // IHttpContextFactory.Dispose(HttpContext) is called.
                //        await ProduceEnd();
                //    }
                //    else if (!HasResponseStarted)
                //    {
                //        // If the request was aborted and no response was sent, there's no
                //        // meaningful status code to log.
                //        StatusCode = 0;
                //    }
                //}

                //if (_onCompleted?.Count > 0)
                //{
                //    await FireOnCompleted();
                //}

                //application.DisposeContext(context, _applicationException);

                //// Even for non-keep-alive requests, try to consume the entire body to avoid RSTs.
                //if (!_connectionAborted && _requestRejectedException == null && !messageBody.IsEmpty)
                //{
                //    await messageBody.ConsumeAsync();
                //}

                //if (HasStartedConsumingRequestBody)
                //{
                //    await messageBody.StopAsync();
                //}
            }
        }

        public void OnStarting(Func<object, Task> callback, object state)
        {
            //if (HasResponseStarted)
            //{
            //    ThrowResponseAlreadyStartedException(nameof(OnStarting));
            //}

            if (_onStarting == null)
            {
                _onStarting = new Stack<KeyValuePair<Func<object, Task>, object>>();
            }
            _onStarting.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
        }

        public void OnCompleted(Func<object, Task> callback, object state)
        {
            if (_onCompleted == null)
            {
                _onCompleted = new Stack<KeyValuePair<Func<object, Task>, object>>();
            }
            _onCompleted.Push(new KeyValuePair<Func<object, Task>, object>(callback, state));
        }

        protected Task FireOnStarting()
        {
            var onStarting = _onStarting;
            if (onStarting?.Count > 0)
            {
                return ProcessEvents(this, onStarting);
            }

            return Task.CompletedTask;

            static async Task ProcessEvents(TerminalProtocol protocol, Stack<KeyValuePair<Func<object, Task>, object>> events)
            {
                // Try/Catch is outside the loop as any error that occurs is before the request starts.
                // So we want to report it as an ApplicationError to fail the request and not process more events.
                try
                {
                    while (events.TryPop(out var entry))
                    {
                        await entry.Key.Invoke(entry.Value);
                    }
                }
                catch (Exception ex)
                {
                    //protocol.ReportApplicationError(ex);
                }
            }
        }

        protected Task FireOnCompleted()
        {
            var onCompleted = _onCompleted;
            if (onCompleted?.Count > 0)
            {
                return ProcessEvents(this, onCompleted);
            }

            return Task.CompletedTask;

            static async Task ProcessEvents(TerminalProtocol protocol, Stack<KeyValuePair<Func<object, Task>, object>> events)
            {
                // Try/Catch is inside the loop as any error that occurs is after the request has finished.
                // So we will just log it and keep processing the events, as the completion has already happened.
                while (events.TryPop(out var entry))
                {
                    try
                    {
                        await entry.Key.Invoke(entry.Value);
                    }
                    catch (Exception ex)
                    {
                        protocol.Log.ApplicationError(protocol.ConnectionId, protocol.TraceIdentifier, ex);
                    }
                }
            }
        }

        public void ProduceContinue()
        {
            throw new NotImplementedException();
        }

        public Memory<byte> GetMemory(int sizeHint = 0)
        {
            throw new NotImplementedException();
        }

        public Span<byte> GetSpan(int sizeHint = 0)
        {
            throw new NotImplementedException();
        }

        public void Advance(int bytes)
        {
            throw new NotImplementedException();
        }

        public ValueTask<FlushResult> FlushPipeAsync(CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public ValueTask<FlushResult> WritePipeAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken)
        {
            throw new NotImplementedException();
        }

        public void CancelPendingFlush()
        {
            throw new NotImplementedException();
        }

        public Task CompleteAsync(Exception exception = null)
        {
            throw new NotImplementedException();
        }
    }


}